{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c612d126",
   "metadata": {},
   "source": [
    "# k 最近邻算法（k-Nearest Neighbors,kNN）\n",
    "简而言之，新数据点离谁最近，就和谁属于同一类。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e26bca5",
   "metadata": {},
   "source": [
    "<img src='img/knn1.jpg' width='300px' />\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e91e27de",
   "metadata": {},
   "source": [
    "**根据 k 最近邻的思想来个中间那个圆点进行分类。**\n",
    "\n",
    "1. 如果 k=3, 圆点的最邻近的三个点是 2个小三角形和 1个小正方形，少数服从多数，基于统计的方法，判定原点的这个待分类点属于三角形一类。\n",
    "\n",
    "\n",
    "2. 如果 k=5, 圆点的最邻近的三个点是 2个小三角形和 3个小正方形，少数服从多数，基于统计的方法，判定原点的这个待分类点属于正方形一类。\n",
    "<br>\n",
    "**从上面的例子可以看出，根据 k最近邻的算法思想如何给新来的点进行归类，只要找到离它最近的 k个实例，哪个类别最多即可。**\n",
    "<br>           \n",
    "        \n",
    "        简单来说，kNN 可以看成：有那么一堆你已经知道分类的数据，当一个新数据进入的时候，就开始跟训练数据里的每个点求距离，然后挑 离这个训练数据最近的 k个点 看看这几个点属于什么类型，最后用少数服从多数的原则，给新数据归类。\n",
    "        \n",
    "        "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4944b4e3",
   "metadata": {},
   "source": [
    "***\n",
    "\n",
    "#### 步骤 \n",
    "（1）存在一个样本数据集，也称为训练样本集，并且样本集中每个数据都存在标签，即我们知道样本中每一个数据与所属分类的对应关系。\n",
    "\n",
    "\n",
    "（2）计算待分类数据与训练样本集中，每一个样本之间的距离\n",
    "\n",
    "\n",
    "（3）找出与待分类样本距离最近的 k个样本\n",
    "\n",
    "\n",
    "（4）观测这 k个样本的分类情况\n",
    "\n",
    "\n",
    "（5）把出现次数最多的类别作为待分类样本的类别"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e0c54d09",
   "metadata": {},
   "source": [
    "<img src='img/knn2.jpg' width='600px'/>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "04479d8f",
   "metadata": {},
   "source": [
    "#### k最近邻算法的步骤：\n",
    "1. 计算已知类别数据集中的点与当前测试点之间的距离。\n",
    "\n",
    "\n",
    "2. 按照距离递增次序排列。\n",
    "\n",
    "\n",
    "3. 选取与当前测试点距离最小的 k个点。\n",
    "\n",
    "\n",
    "4. 确定前 k个点所在类别出现的频率。\n",
    "\n",
    "\n",
    "5. 返回前 k个点所出现频率最高的类别作为当前测试的预测分类。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "787db850",
   "metadata": {},
   "source": [
    "####  两点间距离公式\n",
    "\n",
    "1. **欧几里得距离**\n",
    "<img src='img/欧几里得.jpg' width='300px'/>\n",
    "\n",
    "\n",
    "2. **曼哈顿距离**\n",
    "绝对轴距之和\n",
    "\n",
    "\n",
    "3.切比雪夫距离\n",
    "<img src='img/knn-切比.jpg' width='300px'/>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16e7e10b",
   "metadata": {},
   "source": [
    "**k值**\n",
    "\n",
    "        k值 既不能过大也不能太小（也就是说，选取 k值的关键是实验调参）。k 的取值尽量要取奇数，以保证在计算结果最后会产生一个较多的类别，如果取偶数，则可能会产生相等的情况，不利于预测。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de5dfc0a",
   "metadata": {},
   "source": [
    "**特征归一化**\n",
    "\n",
    "        为了保证每个特征同等重要性，我们对每个特征进行归一化。\n",
    "        \n",
    "        对数据进行归一化，避免量纲对距离计算的影响。\n",
    " \n",
    " \n",
    "|A|B|C| D|E|\n",
    "|:--|:--|:--|:--|:--|\n",
    "[(179,42),男] | [(178,43),男] | [(165,36),女] |[(177,42),男] |[(160,35),女] |\n",
    "\n",
    "第一维身高特征是第二位脚码特征的四倍左右，在进行距离度量的时候，我们就会偏向于第一维特征。\n",
    "\n",
    "这会造成两个特征并不是等价重要的，最终可能会导致距离计算错误，从而导致预测错误。\n",
    "\n",
    "* 样本 **F(167,43)**:\n",
    "    \n",
    "    * AF = $\\sqrt{145}$ \n",
    "    \n",
    "    * BF = $\\sqrt{121}$ \n",
    "    \n",
    "    * CF = $\\sqrt{53}$  ← 最终判断为女性\n",
    "    \n",
    "    * DF = $\\sqrt{101}$ ←\n",
    "    \n",
    "    * EF = $\\sqrt{103}$ ←\n",
    "    \n",
    "***\n",
    "\n",
    "        问题在于，一个女性的脚 43码的可能性，远远小于男性脚 43码的可能。\n",
    "        \n",
    "        由于各个特征量纲的不同，在这里导致了身高的重要性已经远远大于鞋码了，这是不客观的。\n",
    "        \n",
    "        所以我们应该让每个特征都具有同等重要性。\n",
    "**这就是我们要归一化的原因！**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "57d5a4c9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[['宝贝当家', 23.43], ['美人鱼', 18.55], ['澳门风云3', 32.14], ['功夫熊猫3', 21.47], ['谍影重重', 43.87], ['叶问3', 52.01], ['伦敦陷落', 43.42], ['我的特工爷爷', 17.49], ['奔爱', 47.69], ['夜孔雀', 39.66], ['代理情人', 40.57], ['新步步惊心', 34.44]]\n"
     ]
    }
   ],
   "source": [
    "import math\n",
    "movie_data = {\"宝贝当家\": [45, 2, 9, \"喜剧片\"],\n",
    "              \"美人鱼\": [21, 17, 5, \"喜剧片\"],\n",
    "              \"澳门风云3\": [54, 9, 11, \"喜剧片\"],\n",
    "              \"功夫熊猫3\": [39, 0, 31, \"喜剧片\"],\n",
    "              \"谍影重重\": [5, 2, 57, \"动作片\"],\n",
    "              \"叶问3\": [3, 2, 65, \"动作片\"],\n",
    "              \"伦敦陷落\": [2, 3, 55, \"动作片\"],\n",
    "              \"我的特工爷爷\": [6, 4, 21, \"动作片\"],\n",
    "              \"奔爱\": [7, 46, 4, \"爱情片\"],\n",
    "              \"夜孔雀\": [9, 39, 8, \"爱情片\"],\n",
    "              \"代理情人\": [9, 38, 2, \"爱情片\"],\n",
    "              \"新步步惊心\": [8, 34, 17, \"爱情片\"]}\n",
    "\n",
    "# 测试样本  唐人街探案\": [23, 3, 17, \"？片\"]\n",
    "#下面为求与数据集中所有数据的距离代码：\n",
    "x = [23, 3, 17]\n",
    "KNN = []\n",
    "for key,v in movie_data.items():\n",
    "    d = math.sqrt((x[0]-v[0])**2 + (x[1]-v[1])**2 +(x[2]-v[2])**2)\n",
    "    KNN.append([key,round(d,2)])\n",
    "    \n",
    "    # 方法二\n",
    "    # KNN.append([key,round(d,2),v[3]])\n",
    "    \n",
    "# 输出所有电影到 唐探 的距离\n",
    "print(KNN)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "5a479e69",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[['我的特工爷爷', 17.49], ['美人鱼', 18.55], ['功夫熊猫3', 21.47], ['宝贝当家', 23.43], ['澳门风云3', 32.14]]\n"
     ]
    }
   ],
   "source": [
    "KNN.sort(key=lambda dis: dis[1])\n",
    "KNN = KNN[:5]\n",
    "print(KNN)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "e323f347",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "我的特工爷爷\n",
      "[6, 4, 21, '动作片']\n",
      "美人鱼\n",
      "[21, 17, 5, '喜剧片']\n",
      "功夫熊猫3\n",
      "[39, 0, 31, '喜剧片']\n",
      "宝贝当家\n",
      "[45, 2, 9, '喜剧片']\n",
      "澳门风云3\n",
      "[54, 9, 11, '喜剧片']\n",
      "[('喜剧片', 4), ('动作片', 1), ('爱情片', 0)]\n",
      "喜剧片\n"
     ]
    }
   ],
   "source": [
    "# 方法一\n",
    "labels = {'喜剧片':0,'动作片':0,'爱情片':0,}\n",
    "for s in KNN:\n",
    "    print(s[0])\n",
    "    label = movie_data[s[0]]\n",
    "    print(label)\n",
    "    labels[label[3]] += 1\n",
    "labels = sorted(labels.items(),key=lambda l:l[1],reverse=True)\n",
    "\n",
    "print(labels,labels[0][0],sep='\\n')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "32278483",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "喜剧片\n"
     ]
    }
   ],
   "source": [
    "# 方法二\n",
    "# dic ={}\n",
    "# for i in KNN:\n",
    "#     a = i[2]\n",
    "#     dic[a] = dic.get(a,0)+1\n",
    "# list(dic.items())\n",
    "# max = list(dic.items())[0][1]\n",
    "# res = list(dic.items())[0][0]\n",
    "# for _,i in list(dic.items()):\n",
    "#     if  max < i:\n",
    "#         res = _\n",
    "        \n",
    "# print(res)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ceff19aa",
   "metadata": {},
   "source": [
    "***\n",
    "#### 小技巧\n",
    "当待排序列表的元素由多字段构成时，我们可以通过sorted(iterable，key，reverse)的参数key来制定我们根据那个字段对列表元素进行排序。\n",
    "   > key=lambda 元素: 元素[字段索引]\n",
    "\n",
    "例如：想对元素第二个字段排序，则\n",
    "\n",
    "   > key=lambda y: y[1] \n",
    "\n",
    "备注：这里y可以是任意字母，等同\n",
    "   > key=lambda x: x[1]\n",
    "   \n",
    "看几个简单的例子。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "94bdad44",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 1, 3, 6, 8, 9, 10]\n",
      "['a', 'b', 'e', 'g', 't']\n",
      "['a', 'b', 'e', 'g', 't']\n",
      "[('l', 1), ('o', 2), ('v', 3), ('e', 4), ('!', 5)]\n"
     ]
    }
   ],
   "source": [
    "listA = [3, 6, 1, 0, 10, 8, 9]\n",
    "print(sorted(listA))\n",
    "\n",
    "# 一维\n",
    "listB = ['g', 'e', 't', 'b', 'a']\n",
    "print(sorted(listB))\n",
    "print(sorted(listB, key=lambda y: y[0]))\n",
    "\n",
    "# 非一维\n",
    "listC = [('e', 4), ('o', 2), ('!', 5), ('v', 3), ('l', 1)]\n",
    "print(sorted(listC, key=lambda x: x[1]))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7eee5617",
   "metadata": {},
   "source": [
    "> ***"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d627eca",
   "metadata": {},
   "source": [
    "## 参数介绍\n",
    "\n",
    "|参数 |默认|\n",
    "|:--|:--|\n",
    "|**n_neighbors** ：| int，optional(default = 5)|\n",
    "默认情况下kneighbors查询使用的邻居数。就是k-NN的k的值，选取最近的k个点。|\n",
    "|**weights**：| str或callable，可选(默认=‘uniform’)|\n",
    "默认是uniform，参数可以是uniform、distance，也可以是用户自己定义的函数。uniform是均等的权重，就说所有的邻近点的权重都是相等的。distance是不均等的权重，距离近的点比距离远的点的影响大。用户自定义的函数，接收距离的数组，返回一组维数相同的权重。|\n",
    "|**algorithm** ：| {‘auto’，‘ball_tree’，‘kd_tree’，‘brute’}，|\n",
    "可选快速k近邻搜索算法，默认参数为auto，可以理解为算法自己决定合适的搜索算法。除此之外，用户也可以自己指定搜索算法ball_tree、kd_tree、brute方法进行搜索，<br>**brute**是蛮力搜索，也就是线性扫描，当训练集很大时，计算非常耗时。<br>**kd_tree**，构造kd树存储数据以便对其进行快速检索的树形数据结构，<br>**kd树**也就是数据结构中的二叉树。以中值切分构造的树，每个结点是一个超矩形，在维数小于20时效率高。<br>**ball tree**是为了克服kd树高纬失效而发明的，其构造过程是以质心C和半径r分割样本空间，每个节点是一个超球体。|\n",
    "|**leaf_size** ： |int，optional(默认值= 30)|\n",
    "默认是30，这个是构造的kd树和ball树的大小。这个值的设置会影响树构建的速度和搜索速度，同样也影响着存储树所需的内存大小。需要根据问题的性质选择最优的大小。|\n",
    "|**p** ：| 整数，可选(默认= 2)|\n",
    "距离度量公式。在上小结，我们使用欧氏距离公式进行距离度量。除此之外，还有其他的度量方法，例如曼哈顿距离。<br>这个参数**默认为2**，也就是默认使用欧式距离公式进行距离度量。<br>也可以**设置为1**，使用曼哈顿距离公式进行距离度量。<br>也有切比雪夫距离。|\n",
    "|**metric** ：| 字符串或可调用，默认为’minkowski’|\n",
    "用于距离度量，默认度量是minkowski，也就是p=2的欧氏距离(欧几里德度量)。|\n",
    "|**metric_params** ： |dict，optional(默认=None)|\n",
    "距离公式的其他关键参数，这个可以不管，使用默认的None即可。|\n",
    "|**n_jobs** ：| int或None，可选(默认=None)|\n",
    "并行处理设置。默认为1，临近点搜索并行工作数。如果为-1，那么CPU的所有cores都用于并行工作。|"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20f37535",
   "metadata": {},
   "source": [
    "## 使用 scikit-learn 机器学习库内置的kNN算法实现水果识别器\n",
    "\n",
    "**文件fruit_data.csv中包含了 59种水果的数据样本，共5列数据：**\n",
    "\n",
    "* fruit_name : 水果类别\n",
    "* mass : 水果质量\n",
    "* width : 水果的宽度\n",
    "* height : 水果的高度\n",
    "* color_score : 水果的颜色数值，范围 0-1。\n",
    "    * 0.85 - 1.00 红色\n",
    "    * 0.75 - 0.85 橙色\n",
    "    * 0.65 - 0.75 黄色\n",
    "    * 0.45 - 0.65 绿色\n",
    "\n",
    "***\n",
    "\n",
    "* 这里需要利用 Pandas 模块中的 map() 方法进行字符串到数字的映射转换来处理样本的字符串标签。\n",
    "* 调用 scikit-learn 模块中的 KNeighborsClassifier() 建立 kNN 模型，\n",
    "* 调用 scikit-learn 模块中的 fit() 方法训练模型，\n",
    "* 调用 scikit-learn 模块中的 score() 方法验证模型。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "77c7d79b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "# 导入数据集拆分工具\n",
    "from sklearn.model_selection import train_test_split\n",
    "# 导入 KNN 分类模型\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "\n",
    "\n",
    "# 特征文字\n",
    "feat_cols = ['mass','width','height','color_score']\n",
    "\n",
    "# 读取数据\n",
    "data = pd.read_csv('data/fruit_data.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "fa759752",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>fruit_name</th>\n",
       "      <th>mass</th>\n",
       "      <th>width</th>\n",
       "      <th>height</th>\n",
       "      <th>color_score</th>\n",
       "      <th>label</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>apple</td>\n",
       "      <td>192</td>\n",
       "      <td>8.4</td>\n",
       "      <td>7.3</td>\n",
       "      <td>0.55</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>apple</td>\n",
       "      <td>180</td>\n",
       "      <td>8.0</td>\n",
       "      <td>6.8</td>\n",
       "      <td>0.59</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>apple</td>\n",
       "      <td>176</td>\n",
       "      <td>7.4</td>\n",
       "      <td>7.2</td>\n",
       "      <td>0.60</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>mandarin</td>\n",
       "      <td>86</td>\n",
       "      <td>6.2</td>\n",
       "      <td>4.7</td>\n",
       "      <td>0.80</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>mandarin</td>\n",
       "      <td>84</td>\n",
       "      <td>6.0</td>\n",
       "      <td>4.6</td>\n",
       "      <td>0.79</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "  fruit_name  mass  width  height  color_score  label\n",
       "0      apple   192    8.4     7.3         0.55      0\n",
       "1      apple   180    8.0     6.8         0.59      0\n",
       "2      apple   176    7.4     7.2         0.60      0\n",
       "3   mandarin    86    6.2     4.7         0.80      1\n",
       "4   mandarin    84    6.0     4.6         0.79      1"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 预处理\n",
    "fruit2num = {\n",
    "    'apple':0,\n",
    "    \"mandarin\":1,\n",
    "    'orange':2,\n",
    "    'lemon':3\n",
    "}\n",
    "\n",
    "data['label'] = data['fruit_name'].map(fruit2num)\n",
    "data.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "0df9c70d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 取出 X和y\n",
    "X = data[feat_cols].values\n",
    "y = data['label'].values"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b7f1d8c",
   "metadata": {},
   "source": [
    "**对于同一批数据，只要random_state设置地一样，那么其结果也是一样的，即结果可以被复现。**\n",
    "\n",
    "和 random.seed 的作用差不多。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "5ade4c78",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "原始数据集共59个样本，其中训练集样本数为47，测试机样本数为12\n"
     ]
    }
   ],
   "source": [
    "# 划分数据，生成 训练数据集 和 测试数据集\n",
    "'''\n",
    "tip: \n",
    "返回值的顺序是先返回完 x 的 train 和 test\n",
    "再返回 y 的 train 和 test\n",
    "'''\n",
    "X_train_set,X_test_set,y_train_set,y_test_set = train_test_split(X,y\n",
    "                                                                 ,random_state=20\n",
    "                                                                 ,test_size=1/5)\n",
    "print('原始数据集共{}个样本，其中训练集样本数为{}，测试机样本数为{}'.format(\n",
    "                 X.shape[0],X_train_set.shape[0],X_test_set.shape[0]\n",
    "))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "026b8799",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "准确率为91.66666666666666%\n"
     ]
    }
   ],
   "source": [
    "# 训练\n",
    "knn_model = KNeighborsClassifier(n_neighbors=5\n",
    "                                ,weights='distance'\n",
    "                                ,p=2)\n",
    "knn_model.fit(X_train_set,y_train_set)\n",
    "\n",
    "# 准确率检测\n",
    "accur = knn_model.score(X_test_set,y_test_set)\n",
    "\n",
    "print(f'准确率为{accur*100}%')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "4609f035",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{0: 'apple', 1: 'mandarin', 2: 'orange', 3: 'lemon'}"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 测试\n",
    "\n",
    "# 逆向映射\n",
    "num2fruit = dict(zip(fruit2num.values(),fruit2num.keys()))\n",
    "num2fruit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "7202d06c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "第1个测试水果的结果是mandarin，本来应该是mandarin，所以测对了\n",
      "第2个测试水果的结果是apple，本来应该是apple，所以测对了\n",
      "第3个测试水果的结果是apple，本来应该是apple，所以测对了\n",
      "第4个测试水果的结果是lemon，本来应该是lemon，所以测对了\n",
      "第5个测试水果的结果是orange，本来应该是orange，所以测对了\n",
      "第6个测试水果的结果是orange，本来应该是apple，所以测错了\n",
      "第7个测试水果的结果是lemon，本来应该是lemon，所以测对了\n",
      "第8个测试水果的结果是mandarin，本来应该是mandarin，所以测对了\n",
      "第9个测试水果的结果是orange，本来应该是orange，所以测对了\n",
      "第10个测试水果的结果是orange，本来应该是orange，所以测对了\n",
      "第11个测试水果的结果是lemon，本来应该是lemon，所以测对了\n",
      "第12个测试水果的结果是apple，本来应该是apple，所以测对了\n"
     ]
    }
   ],
   "source": [
    "for idx in range(X_test_set.shape[0]):\n",
    "    # print(X_test_set[idx])\n",
    "    test_feat = [X_test_set[idx]]\n",
    "    # .reshape(1, -1)\n",
    "#     print(test_feat.shape\n",
    "    \n",
    "    # 使用 predict 进行预测\n",
    "    y_predict = num2fruit.get(int(knn_model.predict(test_feat)))\n",
    "    y_real = num2fruit.get(y_test_set[idx])\n",
    "    YorN = '对'if y_predict == y_real else '错'\n",
    "    print(f'第{idx+1}个测试水果的结果是{y_predict}，本来应该是{y_real}，所以测{YorN}了')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "761b5769",
   "metadata": {},
   "source": [
    "【报错】**Reshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.**\n",
    "\n",
    "* using：\n",
    "    * array.reshape(-1, 1)====>4×1 \n",
    "    * array.reshape(1, -1)====>1×4"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "33f7bf5b",
   "metadata": {},
   "source": [
    "## kNN 算法优缺点："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e33a396b",
   "metadata": {},
   "source": [
    "【优点】\n",
    "1. kNN 可以处理分类问题，同时天然可以处理多分类问题\n",
    "2. kNN 简单、易懂，同时也很强大，对于手写数字的识别、鸢尾花这一类问题来说，准确率很高。\n",
    "3. kNN 还可以处理回归问题，也就是预测\n",
    "\n",
    "【缺点】\n",
    "1. **效率低**，因为每一次分类和回归，都需要把训练数据和测试数据都训练一遍，如果数据量很大的话，需要的算力会很惊人，但是，大数据处理有时很常见的一件事。\n",
    "2. 对训练数据**依赖度特别大**，kNN 尤为严重，因为样本数据也有错误的情况，这会导致预测的数据的不准确，**对训练数据的容错性太差**。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c7e13f56",
   "metadata": {},
   "source": [
    "**维度灾难！** kNN对于多维度的数据处理也不是很好，随着维度的增加，“看似相近” 的两个点之间的距离越来越大，对于 kNN 这种高度依赖距离的算法来说，这个也会影响准确率。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "796c7868",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e2bc9ca8",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f3d9ddae",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c7dfbdcc",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "00c65d0f",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
